#!/usr/bin/python
# Copyright 2020 Makani Technologies LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Generate the message_type.[ch] files."""

import os
import re
import sys
import textwrap

from makani.avionics.network import network_config


def _WriteMessageHeader(script_name, file_without_extension, rel_path,
                        message_types):
  """Writes message_type.h header file.

  Args:
    script_name: This script's filename.
    file_without_extension: The full path to the output file, missing the '.c'.
    rel_path: The relative path from the autogenerated files root to
              file_without_extension.
    message_types: A sorted list of MessageTypes.
  """

  assert len(set([m.enum_prefix for m in message_types])) == 1
  enum_prefix = message_types[0].enum_prefix

  header_guard = re.sub('[/.]', '_', rel_path.upper()) + '_H_'
  parts = [textwrap.dedent("""
      #ifndef {guard}
      #define {guard}

      // Generated by {name}; do not edit.

      #include <stdbool.h>

      #ifdef __cplusplus
      extern "C" {{
      #endif

      typedef enum {{"""[1:]).format(name=script_name, guard=header_guard)]
  for m in message_types:
    parts.append('  %s = %d,' % (m.enum_name, m.enum_value))
  num_message_types = max([m.enum_value for m in message_types]) + 1

  parts.append(textwrap.dedent("""
        kNum{prefix}s = {num_message_types}
      }} {prefix};

      const char *{prefix}ToString({prefix} message_type);
      const char *{prefix}ToShortString({prefix} message_type);
      bool IsValid{prefix}({prefix} message_type);
      {prefix} StringTo{prefix}(const char *message_type);

      #ifdef __cplusplus
      }}  // extern "C"
      #endif

      #endif  // {guard}\n"""[1:]).format(prefix=enum_prefix,
                                          guard=header_guard,
                                          num_message_types=num_message_types))

  with open(file_without_extension + '.h', 'w') as f:
    f.write('\n'.join(parts))


def _WriteMessageSource(script_name, file_without_extension, rel_path,
                        message_types):
  """Writes message_type.c source file.

  Args:
    script_name: This script's filename.
    file_without_extension: The full path to the output file, missing the '.c'.

    rel_path: The relative path from the autogenerated files root to
              file_without_extension.
    message_types: A sorted list of MessageTypes.
  """
  assert len(set([m.enum_prefix for m in message_types])) == 1
  enum_prefix = message_types[0].enum_prefix

  parts = [textwrap.dedent("""
      // Generated by {name}; do not edit.

      #include "{header}"

      #include <assert.h>
      #include <string.h>

      const char *{prefix}ToString({prefix} message_type) {{
        switch (message_type) {{
          // Fall-through intentional.
          default:
          case kNum{prefix}s:
            assert(0);
            return "<unknown>";"""[1:]).format(prefix=enum_prefix,
                                               name=script_name,
                                               header=rel_path + '.h')]
  for m in message_types:
    parts.append('    case %s:' % m.enum_name)
    parts.append('      return "%s";' % m.enum_name)
  parts.append('  }')
  parts.append('}\n')

  parts.append(textwrap.dedent("""\
      const char *{prefix}ToShortString({prefix} message_type) {{
        if (IsValid{prefix}(message_type)) {{
          return {prefix}ToString(message_type) + strlen("k{prefix}");
        }} else {{
          return "<error>";
        }}
      }}
      """.format(prefix=enum_prefix)))

  parts.append(textwrap.dedent("""
      bool IsValid{prefix}({prefix} message_type) {{
        switch (message_type) {{
          // Fall-through intentional.
          default:
          case kNum{prefix}s:
            return false;"""[1:]).format(prefix=enum_prefix,
                                         name=script_name,
                                         header=rel_path + '.h'))
  for m in message_types:
    parts.append('    case %s:' % m.enum_name)
  parts.append('      return true;')
  parts.append('  }')
  parts.append('}\n')

  parts.append(textwrap.dedent("""
      {prefix} StringTo{prefix}(const char *message_type) {{
        for (unsigned int i = 0; i < kNum{prefix}s; ++i) {{
          if (IsValid{prefix}(i)) {{
            const char *name = {prefix}ToString(i);
            if (!strcmp(name, message_type))
              return i;
            name = {prefix}ToShortString(i);
            if (!strcmp(name, message_type))
              return i;
          }}
        }}
        return kNum{prefix}s;
      }}
      """[1:]).format(prefix=enum_prefix))

  with open(file_without_extension + '.c', 'w') as f:
    f.write('\n'.join(parts))


def _WriteMessageType(autogen_root, script_name, message_types,
                      output_dir, output_file_without_extension):
  """Writes message_type.[ch] files.

  Args:
    autogen_root: The MAKANI_HOME-equivalent top directory.
    script_name: This script's filename.
    message_types: The 'message_types' field of the YAML file.
    output_dir: The directory in which to output the files.
    output_file_without_extension: The output file prefix.
  """
  file_without_extension = os.path.join(output_dir,
                                        output_file_without_extension)
  rel_path = os.path.relpath(file_without_extension, autogen_root)

  _WriteMessageHeader(script_name, file_without_extension, rel_path,
                      message_types)
  _WriteMessageSource(script_name, file_without_extension, rel_path,
                      message_types)


def main(argv):
  flags, argv = network_config.ParseGenerationFlags(argv)

  config = network_config.NetworkConfig(flags.network_file)
  script_name = os.path.basename(argv[0])
  _WriteMessageType(flags.autogen_root, script_name, config.aio_messages,
                    flags.output_dir, 'message_type')
  _WriteMessageType(flags.autogen_root, script_name, config.eop_messages,
                    flags.output_dir, 'eop_message_type')
  _WriteMessageType(flags.autogen_root, script_name, config.winch_messages,
                    flags.output_dir, 'winch_message_type')

if __name__ == '__main__':
  main(sys.argv)
